﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace WindowsGame1
{
    class Vectorfield
    {
        #region protected members
            protected DataVector[,] _dataVectors;
            protected int _curTimestep;
            protected int _width;
            protected int _height;
            protected float _maxPressure = -1000;
            protected float _minPressure = -1000;

        /// <summary>
        /// for resizing the vector info (x-axis)
        /// </summary>
            protected float _horizontalResizeValue;
        /// <summary>
        /// for resizing the vector info (y-axis)
        /// </summary>
            protected float _verticalResizeValue;
        #endregion

        #region constructor
        public Vectorfield(int timestep, int width, int height, float[, ,] dataArray, int ViewportWidth, int ViewportHeight)
        {

            _curTimestep = timestep;
            _dataVectors = new DataVector[width, height];
            _width = width;
            _height = height;
            
            //calculate resize values
            _horizontalResizeValue = _width / (float)ViewportWidth;
            _verticalResizeValue = _height / (float)ViewportHeight;

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    _dataVectors[x, y] = new DataVector(this);
                    _dataVectors[x, y].position = new Vector2(x, y);
                    _dataVectors[x, y].pressure = dataArray[_curTimestep, x, y];

                }
            }
        }

        #endregion


        #region public functions

        public DataVector getDataVectorAt(Vector2 pos, bool isInScreenCoordinates)
        {
            int x;
            int y;
            if (isInScreenCoordinates)
            {
                //should resize correctly
                x = (int)Math.Round(_horizontalResizeValue * pos.X);
                y = (int)Math.Round(_verticalResizeValue * pos.Y);
            }
            else
            {
                //should resize correctly
                x = (int)Math.Round(pos.X);
                y = (int)Math.Round(pos.Y);

            }

            return _dataVectors[x, y];
        }
        
        /// <summary>
        /// interpolates pressure of the nearest vectors at position
        /// </summary>
        /// <param name="pos">the position in SCREEN coordinates</param>
        /// <returns>pressure of the closes DataVector</returns>
        public float InterpolatePressureAt(Vector2 pos, bool isInScreenCoordinates)
        {
            int x;
            int y;
            if (isInScreenCoordinates)
            {
                //should resize correctly
                x = (int)Math.Round(_horizontalResizeValue * pos.X);
                y = (int)Math.Round(_verticalResizeValue * pos.Y);
            }
            else
            {
                //should resize correctly
                x = (int)Math.Round(pos.X);
                y = (int)Math.Round(pos.Y);

            }
               
           //TODO: implement interpolation!

           return _dataVectors[x, y].pressure;
        }

        /// <summary>
        /// returns maximum pressure of DataVector
        /// </summary>
        /// <returns>maxPressure</returns>
        public float getMaxPressure()
        {
            if (_maxPressure == -1000)
                calculateMinMaxValues();

            return _maxPressure;
        }

        /// <summary>
        /// returns minimum pressure of DataVector
        /// </summary>
        /// <returns>minPressure</returns>
        public float getMinPressure()
        {
            if (_minPressure == -1000)
                calculateMinMaxValues();

            return _minPressure;
        }

        /// <summary>
        /// calculates min and max Pressure of DataVector
        /// </summary>
        private void calculateMinMaxValues()
        {
            float min = _dataVectors[0, 0].pressure, 
                    max = min;

            for (int i = 0; i < _dataVectors.GetLength(0); i++)
            {
                for (int j = 0; j < _dataVectors.GetLength(1); j++)
                {
                    if (_dataVectors[i, j].pressure < min)
                    {
                        min = _dataVectors[i, j].pressure;
                    }
                    if (_dataVectors[i, j].pressure > max)
                    {
                        max = _dataVectors[i, j].pressure;
                    }
                }
            }
            _maxPressure = max;
            _minPressure = min;
        }

        //
        public Vector2 BilinearInterpolateDirection(Vector2 position, bool isInScreenCoordinates)
        {
            Vector2 interpolatedPointY1;
            Vector2 interpolatedPointY2;
            Vector2 interpolatedDirection;

            //Interpolation: Get the Neighborhood and calculate gradients
            Vector2 position1 = position;
            Vector2 position2;
            if (isInScreenCoordinates)
            {
                position2 = position + new Vector2(10 , 10 );
            }
            else
            {
                position2 = position + Vector2.One;
            }


            Vector2 grad11 = this.getDataVectorAt(position1, isInScreenCoordinates).direction;
            Vector2 grad21 = this.getDataVectorAt(new Vector2(position2.X, position2.Y), isInScreenCoordinates).direction;
            Vector2 grad12 = this.getDataVectorAt(new Vector2(position1.X, position2.Y), isInScreenCoordinates).direction;
            Vector2 grad22 = this.getDataVectorAt(position2, isInScreenCoordinates).direction;
            //bilinear interpolation

            interpolatedPointY1 = (((position2.X - position.X) / (position2.X - position1.X)) * grad11) + (((position.X - position1.X) / (position2.X - position1.X) * grad21));
            interpolatedPointY2 = (((position2.X - position.X) / (position2.X - position1.X)) * grad12) + (((position.X - position1.X) / (position2.X - position1.X) * grad22));

            return interpolatedDirection = (((position2.Y - position.Y) / (position2.Y - position1.Y)) * interpolatedPointY1) + (((position.Y - position1.Y) / (position2.Y - position1.Y) * interpolatedPointY2));

        }
        /// <summary>
        /// calculate the direction by using gradients
        /// </summary>
        /// <param name="pos">the position where the direction should be calculated --> VECTORFIELD COORDINATES</param>
        /// <returns>the new direction at this position</returns>
        public Vector2 CalculateDirection(Vector2 pos)
        {
            //should resize correctly
            int x = (int)Math.Round(pos.X);
            int y = (int)Math.Round(pos.Y);

            float currentPressure = _dataVectors[x, y].pressure;
            Vector2 direction = new Vector2(0, 0);


            // Gradientberechnung mit Randbehandlung
            if (x == 0) { direction.X = _dataVectors[x + 1, y].pressure - _dataVectors[x, y].pressure; }
            else if (x == _width - 1) { direction.X = _dataVectors[x, y].pressure - _dataVectors[x - 1, y].pressure; }
            else { direction.X = (_dataVectors[x + 1, y].pressure - _dataVectors[x - 1, y].pressure) / 2; }
            if (y == 0) { direction.Y = _dataVectors[x, y + 1].pressure - _dataVectors[x, y].pressure; }
            else if (y == _height - 1) { direction.Y = _dataVectors[x, y].pressure - _dataVectors[x, y - 1].pressure; }
            else { direction.Y = (_dataVectors[x, y + 1].pressure - _dataVectors[x, y - 1].pressure) / 2; }

            return new Vector2(direction.Y, -direction.X);
        }
        #endregion


        #region properties

        /// <summary>
        /// gets the width of the data
        /// </summary>
        public int width
        {
            get 
            {
                return _width;
            }
        }

        /// <summary>
        /// gets the height of the data
        /// </summary>
        public int height
        {
            get
            {
                return _height;
            }
        }

        #endregion

        
    }
}
